<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 2.0.10">
<title>OAuth2 Boot</title>
<style>
@import url(http://fonts.googleapis.com/css?family=Titillium+Web:400,700);
@import url(http://fonts.googleapis.com/css?family=Noticia+Text:400,400italic);
@import url(http://cdnjs.cloudflare.com/ajax/libs/font-awesome/3.2.0/css/font-awesome.css);
/* Derived from the Riak documentation theme developed by Basho Technologies, Inc. | CC BY 3.0 License | http://docs.basho.org */
/* normalize.css v2.1.1 | MIT License | git.io/normalize */
/* ========================================================================== HTML5 display definitions ========================================================================== */
/** Correct `block` display not defined in IE 8/9. */
article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary {
    display: block;
}

/** Correct `inline-block` display not defined in IE 8/9. */
audio, canvas, video {
    display: inline-block;
}

/** Prevent modern browsers from displaying `audio` without controls. Remove excess height in iOS 5 devices. */
audio:not([controls]) {
    display: none;
    height: 0;
}

/** Address styling not present in IE 8/9. */
[hidden] {
    display: none;
}

/* ========================================================================== Base ========================================================================== */
/** 1. Prevent system color scheme's background color being used in Firefox, IE, and Opera. 2. Prevent system color scheme's text color being used in Firefox, IE, and Opera. 3. Set default font family to sans-serif. 4. Prevent iOS text size adjust after orientation change, without disabling user zoom. */
html {
    background: #fff; /* 1 */
    color: #000; /* 2 */
    font-family: sans-serif; /* 3 */
    -ms-text-size-adjust: 100%; /* 4 */
    -webkit-text-size-adjust: 100%; /* 4 */
}

/** Remove default margin. */
body {
    margin: 0;
}

/* ========================================================================== Links ========================================================================== */
/** Address `outline` inconsistency between Chrome and other browsers. */
a:focus {
    outline: thin dotted;
}

/** Improve readability when focused and also mouse hovered in all browsers. */
a:active, a:hover {
    outline: 0;
}

/* ========================================================================== Typography ========================================================================== */
/** Address variable `h1` font-size and margin within `section` and `article` contexts in Firefox 4+, Safari 5, and Chrome. */
h1 {
    font-size: 2em;
    margin: 0.67em 0;
}

/** Address styling not present in IE 8/9, Safari 5, and Chrome. */
abbr[title] {
    border-bottom: 1px dotted;
}

/** Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */
b, strong {
    font-weight: bold;
}

/** Address styling not present in Safari 5 and Chrome. */
dfn {
    font-style: italic;
}

/** Address differences between Firefox and other browsers. */
hr {
    -moz-box-sizing: content-box;
    box-sizing: content-box;
    height: 0;
}

/** Address styling not present in IE 8/9. */
mark {
    background: #ff0;
    color: #000;
}

/** Correct font family set oddly in Safari 5 and Chrome. */
code, kbd, pre, samp {
    font-family: monospace, serif;
    font-size: 1em;
}

/** Improve readability of pre-formatted text in all browsers. */
pre {
    white-space: pre-wrap;
}

/** Set consistent quote types. */
q {
    quotes: "\201C" "\201D" "\2018" "\2019";
}

/** Address inconsistent and variable font size in all browsers. */
small {
    font-size: 80%;
}

/** Prevent `sub` and `sup` affecting `line-height` in all browsers. */
sub, sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
}

sup {
    top: -0.5em;
}

sub {
    bottom: -0.25em;
}

/* ========================================================================== Embedded content ========================================================================== */
/** Remove border when inside `a` element in IE 8/9. */
img {
    border: 0;
}

/** Correct overflow displayed oddly in IE 9. */
svg:not(:root) {
    overflow: hidden;
}

/* ========================================================================== Figures ========================================================================== */
/** Address margin not present in IE 8/9 and Safari 5. */
figure {
    margin: 0;
}

/* ========================================================================== Forms ========================================================================== */
/** Define consistent border, margin, and padding. */
fieldset {
    border: 1px solid #c0c0c0;
    margin: 0 2px;
    padding: 0.35em 0.625em 0.75em;
}

/** 1. Correct `color` not being inherited in IE 8/9. 2. Remove padding so people aren't caught out if they zero out fieldsets. */
legend {
    border: 0; /* 1 */
    padding: 0; /* 2 */
}

/** 1. Correct font family not being inherited in all browsers. 2. Correct font size not being inherited in all browsers. 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. */
button, input, select, textarea {
    font-family: inherit; /* 1 */
    font-size: 100%; /* 2 */
    margin: 0; /* 3 */
}

/** Address Firefox 4+ setting `line-height` on `input` using `!important` in the UA stylesheet. */
button, input {
    line-height: normal;
}

/** Address inconsistent `text-transform` inheritance for `button` and `select`. All other form control elements do not inherit `text-transform` values. Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. Correct `select` style inheritance in Firefox 4+ and Opera. */
button, select {
    text-transform: none;
}

/** 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. 2. Correct inability to style clickable `input` types in iOS. 3. Improve usability and consistency of cursor style between image-type `input` and others. */
button, html input[type="button"], input[type="reset"], input[type="submit"] {
    -webkit-appearance: button; /* 2 */
    cursor: pointer; /* 3 */
}

/** Re-set default cursor for disabled elements. */
button[disabled], html input[disabled] {
    cursor: default;
}

/** 1. Address box sizing set to `content-box` in IE 8/9. 2. Remove excess padding in IE 8/9. */
input[type="checkbox"], input[type="radio"] {
    box-sizing: border-box; /* 1 */
    padding: 0; /* 2 */
}

/** 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome (include `-moz` to future-proof). */
input[type="search"] {
    -webkit-appearance: textfield; /* 1 */
    -moz-box-sizing: content-box;
    -webkit-box-sizing: content-box; /* 2 */
    box-sizing: content-box;
}

/** Remove inner padding and search cancel button in Safari 5 and Chrome on OS X. */
input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration {
    -webkit-appearance: none;
}

/** Remove inner padding and border in Firefox 4+. */
button::-moz-focus-inner, input::-moz-focus-inner {
    border: 0;
    padding: 0;
}

/** 1. Remove default vertical scrollbar in IE 8/9. 2. Improve readability and alignment in all browsers. */
textarea {
    overflow: auto; /* 1 */
    vertical-align: top; /* 2 */
}

/* ========================================================================== Tables ========================================================================== */
/** Remove most spacing between table cells. */
table {
    border-collapse: collapse;
    border-spacing: 0;
}

*, *:before, *:after {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
}

html, body {
    font-size: 100%;
}

body {
    background: white;
    color: #222222;
    padding: 0;
    margin: 0;
    font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
    font-weight: normal;
    font-style: normal;
    line-height: 1;
    position: relative;
    cursor: auto;
}

a:hover {
    cursor: pointer;
}

a:focus {
    outline: none;
}

img, object, embed {
    max-width: 100%;
    height: auto;
}

object, embed {
    height: 100%;
}

img {
    -ms-interpolation-mode: bicubic;
}

#map_canvas img, #map_canvas embed, #map_canvas object, .map_canvas img, .map_canvas embed, .map_canvas object {
    max-width: none !important;
}

.left {
    float: left !important;
}

.right {
    float: right !important;
}

.text-left {
    text-align: left !important;
}

.text-right {
    text-align: right !important;
}

.text-center {
    text-align: center !important;
}

.text-justify {
    text-align: justify !important;
}

.hide {
    display: none;
}

.antialiased, body {
    -webkit-font-smoothing: antialiased;
}

img {
    display: inline-block;
    vertical-align: middle;
}

textarea {
    height: auto;
    min-height: 50px;
}

select {
    width: 100%;
}

p.lead, .paragraph.lead > p, #preamble > .sectionbody > .paragraph:first-of-type p {
    font-size: 1.21875em;
    line-height: 1.6;
}

.subheader, #content #toctitle, .admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .videoblock > .title, .listingblock > .title, .literalblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title, .tableblock > caption {
    line-height: 1.4;
    color: #3c3d3f;
    font-weight: 300;
    margin-top: 0.2em;
    margin-bottom: 0.5em;
}

/* Typography resets */
div, dl, dt, dd, ul, ol, li, h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6, pre, form, p, blockquote, th, td {
    margin: 0;
    padding: 0;
    direction: ltr;
}

/* Default Link Styles */
a {
    color: #2984a9;
    text-decoration: underline;
    line-height: inherit;
}

a:hover, a:focus {
    color: #faa94c;
}

a img {
    border: none;
}

/* Default paragraph styles */
p {
    font-family: inherit;
    font-weight: normal;
    font-size: 1em;
    line-height: 1.5;
    margin-bottom: 0.9375em;
    text-rendering: geometricPrecision;
}

p aside {
    font-size: 0.875em;
    line-height: 1.35;
    font-style: italic;
}

/* Default header styles */
h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 {
    font-family: "Titillium Web", Verdana, Arial, sans-serif;
    font-weight: normal;
    font-style: normal;
    color: #616366;
    text-rendering: geometricPrecision;
    margin-top: 0.5em;
    margin-bottom: 0.5em;
    line-height: 1.2125em;
}

h1 small, h2 small, h3 small, #toctitle small, .sidebarblock > .content > .title small, h4 small, h5 small, h6 small {
    font-size: 60%;
    color: #aeb0b2;
    line-height: 0;
}

h1 {
    font-size: 2.125em;
}

h2 {
    font-size: 1.6875em;
}

h3, #toctitle, .sidebarblock > .content > .title {
    font-size: 1.375em;
}

h4 {
    font-size: 1.125em;
}

h5 {
    font-size: 1.125em;
}

h6 {
    font-size: 1em;
}

hr {
    border: solid #dddddd;
    border-width: 1px 0 0;
    clear: both;
    margin: 1.25em 0 1.1875em;
    height: 0;
}

/* Helpful Typography Defaults */
em, i {
    font-style: italic;
    line-height: inherit;
}

strong, b {
    font-weight: bold;
    line-height: inherit;
}

small {
    font-size: 60%;
    line-height: inherit;
}

code {
    font-family: monospace, serif;
    font-weight: normal;
    color: #515151;
}

/* Lists */
ul, ol, dl {
    font-size: 1em;
    line-height: 1.5;
    margin-bottom: 0.9375em;
    list-style-position: outside;
    font-family: inherit;
}

ul, ol {
    margin-left: 1.5em;
}

/* Unordered Lists */
ul li ul, ul li ol {
    margin-left: 1.25em;
    margin-bottom: 0;
    font-size: 1em; /* Override nested font-size change */
}

ul.square li ul, ul.circle li ul, ul.disc li ul {
    list-style: inherit;
}

ul.square {
    list-style-type: square;
}

ul.circle {
    list-style-type: circle;
}

ul.disc {
    list-style-type: disc;
}

ul.no-bullet {
    list-style: none;
}

/* Ordered Lists */
ol li ul, ol li ol {
    margin-left: 1.25em;
    margin-bottom: 0;
}

/* Definition Lists */
dl dt {
    margin-bottom: 0.3125em;
    font-weight: bold;
}

dl dd {
    margin-bottom: 1.25em;
}

/* Abbreviations */
abbr, acronym {
    text-transform: uppercase;
    font-size: 90%;
    color: #515151;
    border-bottom: 1px dotted #dddddd;
    cursor: help;
}

abbr {
    text-transform: none;
}

/* Blockquotes */
blockquote {
    margin: 0 0 0.9375em;
    padding: 0.5625em 1.25em 0 1.1875em;
    border-left: 1px solid #dddddd;
}

blockquote cite {
    display: block;
    font-size: inherit;
    color: #484a4c;
}

blockquote cite:before {
    content: "\2014 \0020";
}

blockquote cite a, blockquote cite a:visited {
    color: #484a4c;
}

blockquote, blockquote p {
    line-height: 1.5;
    color: #616366;
}

/* Microformats */
.vcard {
    display: inline-block;
    margin: 0 0 1.25em 0;
    border: 1px solid #dddddd;
    padding: 0.625em 0.75em;
}

.vcard li {
    margin: 0;
    display: block;
}

.vcard .fn {
    font-weight: bold;
    font-size: 0.9375em;
}

.vevent .summary {
    font-weight: bold;
}

.vevent abbr {
    cursor: auto;
    text-decoration: none;
    font-weight: bold;
    border: none;
    padding: 0 0.0625em;
}

@media only screen and (min-width: 768px) {
    h1, h2, h3, #toctitle, .sidebarblock > .content > .title, h4, h5, h6 {
        line-height: 1.4;
    }

    h1 {
        font-size: 2.75em;
    }

    h2 {
        font-size: 2.3125em;
    }

    h3, #toctitle, .sidebarblock > .content > .title {
        font-size: 1.6875em;
    }

    h4 {
        font-size: 1.4375em;
    }
}

/* Print styles.  Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/ Credit to Paul Irish and HTML5 Boilerplate (html5boilerplate.com)
*/
.print-only {
    display: none !important;
}

@media print {
    * {
        background: transparent !important;
        color: #000 !important; /* Black prints faster: h5bp.com/s */
        box-shadow: none !important;
        text-shadow: none !important;
    }

    a, a:visited {
        text-decoration: underline;
    }

    a[href]:after {
        content: " (" attr(href) ")";
    }

    abbr[title]:after {
        content: " (" attr(title) ")";
    }

    .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after {
        content: "";
    }

    pre, blockquote {
        border: 1px solid #999;
        page-break-inside: avoid;
    }

    thead {
        display: table-header-group; /* h5bp.com/t */
    }

    tr, img {
        page-break-inside: avoid;
    }

    img {
        max-width: 100% !important;
    }

    @page {
        margin: 0.5cm;
    }

    p, h2, h3, #toctitle, .sidebarblock > .content > .title {
        orphans: 3;
        widows: 3;
    }

    h2, h3, #toctitle, .sidebarblock > .content > .title {
        page-break-after: avoid;
    }

    .hide-on-print {
        display: none !important;
    }

    .print-only {
        display: block !important;
    }

    .hide-for-print {
        display: none !important;
    }

    .show-for-print {
        display: inherit !important;
    }
}

/* Tables */
table {
    background: #f1f1f1;
    margin-bottom: 1.25em;
    border: solid 5px white;
}

table thead, table tfoot {
    background: #dfdfdf;
    font-weight: bold;
}

table thead tr th, table thead tr td, table tfoot tr th, table tfoot tr td {
    padding: 0.5em 0.625em 0.625em;
    font-size: inherit;
    color: #222222;
    text-align: left;
}

table tr th, table tr td {
    padding: 0.5625em 0.625em;
    font-size: inherit;
    color: #222222;
}

table tr.even, table tr.alt, table tr:nth-of-type(even) {
    background: #dfdfdf;
}

table thead tr th, table tfoot tr th, table tbody tr td, table tr td, table tfoot tr td {
    display: table-cell;
    line-height: 1.5;
}

.clearfix:before, .clearfix:after, .float-group:before, .float-group:after {
    content: " ";
    display: table;
}

.clearfix:after, .float-group:after {
    clear: both;
}

*:not(pre) > code {
    font-size: 0.9em;
    padding: 0 3px;
    white-space: nowrap;
    background-color: #dfdfdf;
    border: 1px solid #c9c9c9;
    -webkit-border-radius: 4px;
    border-radius: 4px;
    text-shadow: none;
}

pre, pre > code {
    line-height: 1.6;
    color: inherit;
    font-family: monospace, serif;
    font-weight: normal;
}

kbd.keyseq {
    color: #848484;
}

kbd:not(.keyseq) {
    display: inline-block;
    color: #515151;
    font-size: 0.75em;
    line-height: 1.4;
    background-color: #F7F7F7;
    border: 1px solid #ccc;
    -webkit-border-radius: 3px;
    border-radius: 3px;
    -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset;
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 0 0 2px white inset;
    margin: -0.15em 0.15em 0 0.15em;
    padding: 0.2em 0.6em 0.2em 0.5em;
    vertical-align: middle;
    white-space: nowrap;
}

kbd kbd:first-child {
    margin-left: 0;
}

kbd kbd:last-child {
    margin-right: 0;
}

.menuseq, .menu {
    color: #373737;
}

p a > code:hover {
    color: #444444;
}

#header, #content, #footnotes, #footer {
    width: 100%;
    margin-left: auto;
    margin-right: auto;
    margin-top: 0;
    margin-bottom: 0;
    max-width: 62.5em;
    *zoom: 1;
    position: relative;
    padding-left: 0.9375em;
    padding-right: 0.9375em;
}

#header:before, #header:after, #content:before, #content:after, #footnotes:before, #footnotes:after, #footer:before, #footer:after {
    content: " ";
    display: table;
}

#header:after, #content:after, #footnotes:after, #footer:after {
    clear: both;
}

#header {
    margin-bottom: 2.5em;
}

#header > h1 {
    color: #616366;
    font-weight: normal;
    border-bottom: 1px solid #dddddd;
    margin-bottom: -28px;
    padding-bottom: 32px;
}

#header span {
    color: #616366;
}

#header #revnumber {
    text-transform: capitalize;
}

#header br {
    display: none;
}

#header br + span {
    padding-left: 3px;
}

#header br + span:before {
    content: "\2013 \0020";
}

#header br + span.author {
    padding-left: 0;
}

#header br + span.author:before {
    content: ", ";
}

#toc {
    border-bottom: 1px dashed #cccccc;
    padding-bottom: 1.25em;
}

#toc > ul {
    margin-left: 0.25em;
}

#toc ul.sectlevel0 > li > a {
    font-style: italic;
}

#toc ul.sectlevel0 ul.sectlevel1 {
    margin-left: 0;
    margin-top: 0.5em;
    margin-bottom: 0.5em;
}

#toc ul {
    list-style-type: none;
}

#toctitle {
    color: #3c3d3f;
}

@media only screen and (min-width: 1280px) {
    body.toc2 {
        padding-left: 20em;
    }

    #toc.toc2 {
        position: fixed;
        width: 20em;
        left: 0;
        top: 0;
        border-right: 1px solid #cccccc;
        border-bottom: 0;
        z-index: 1000;
        padding: 1em;
        height: 100%;
        overflow: auto;
    }

    #toc.toc2 #toctitle {
        margin-top: 0;
    }

    #toc.toc2 > ul {
        font-size: .95em;
    }

    #toc.toc2 ul ul {
        margin-left: 0;
        padding-left: 1.25em;
    }

    #toc.toc2 ul.sectlevel0 ul.sectlevel1 {
        padding-left: 0;
        margin-top: 0.5em;
        margin-bottom: 0.5em;
    }

    body.toc2.toc-right {
        padding-left: 0;
        padding-right: 20em;
    }

    body.toc2.toc-right #toc.toc2 {
        border-right: 0;
        border-left: 1px solid #cccccc;
        left: auto;
        right: 0;
    }
}

#content #toc {
    border-style: solid;
    border-width: 1px;
    border-color: #d9d9d9;
    margin-bottom: 1.25em;
    padding: 1.25em;
    background: #f2f2f2;
    border-width: 0;
    -webkit-border-radius: 4px;
    border-radius: 4px;
}

#content #toc > :first-child {
    margin-top: 0;
}

#content #toc > :last-child {
    margin-bottom: 0;
}

#content #toc a {
    text-decoration: none;
}

#content #toctitle {
    font-weight: bold;
    font-family: "Titillium Web", Verdana, Arial, sans-serif;
    font-size: 1em;
    padding-left: 0.125em;
}

#footer {
    max-width: 100%;
    background-color: #515151;
    padding: 1.25em;
}

#footer-text {
    color: #aeaeae;
    line-height: 1.35;
}

.sect1 {
    padding-bottom: 1.25em;
}

.sect1 + .sect1 {
    border-top: 1px dashed #cccccc;
}

#content h1 > a.anchor, h2 > a.anchor, h3 > a.anchor, #toctitle > a.anchor, .sidebarblock > .content > .title > a.anchor, h4 > a.anchor, h5 > a.anchor, h6 > a.anchor {
    position: absolute;
    width: 1em;
    margin-left: -1em;
    display: block;
    text-decoration: none;
    visibility: hidden;
    text-align: center;
    font-weight: normal;
}

#content h1 > a.anchor:before, h2 > a.anchor:before, h3 > a.anchor:before, #toctitle > a.anchor:before, .sidebarblock > .content > .title > a.anchor:before, h4 > a.anchor:before, h5 > a.anchor:before, h6 > a.anchor:before {
    content: '\00A7';
    font-size: .85em;
    vertical-align: text-top;
    display: block;
    margin-top: 0.05em;
}

#content h1:hover > a.anchor, #content h1 > a.anchor:hover, h2:hover > a.anchor, h2 > a.anchor:hover, h3:hover > a.anchor, #toctitle:hover > a.anchor, .sidebarblock > .content > .title:hover > a.anchor, h3 > a.anchor:hover, #toctitle > a.anchor:hover, .sidebarblock > .content > .title > a.anchor:hover, h4:hover > a.anchor, h4 > a.anchor:hover, h5:hover > a.anchor, h5 > a.anchor:hover, h6:hover > a.anchor, h6 > a.anchor:hover {
    visibility: visible;
}

#content h1 > a.link, h2 > a.link, h3 > a.link, #toctitle > a.link, .sidebarblock > .content > .title > a.link, h4 > a.link, h5 > a.link, h6 > a.link {
    color: #616366;
    text-decoration: none;
}

#content h1 > a.link:hover, h2 > a.link:hover, h3 > a.link:hover, #toctitle > a.link:hover, .sidebarblock > .content > .title > a.link:hover, h4 > a.link:hover, h5 > a.link:hover, h6 > a.link:hover {
    color: #555659;
}

.imageblock, .literalblock, .listingblock, .verseblock, .videoblock {
    margin-bottom: 1.25em;
}

.admonitionblock td.content > .title, .exampleblock > .title, .imageblock > .title, .videoblock > .title, .listingblock > .title, .literalblock > .title, .openblock > .title, .paragraph > .title, .quoteblock > .title, .sidebarblock > .title, .tableblock > .title, .verseblock > .title, .dlist > .title, .olist > .title, .ulist > .title, .qlist > .title, .hdlist > .title {
    text-align: left;
    font-weight: bold;
}

.tableblock > caption {
    text-align: left;
    font-weight: bold;
    white-space: nowrap;
    overflow: visible;
    max-width: 0;
}

table.tableblock #preamble > .sectionbody > .paragraph:first-of-type p {
    font-size: inherit;
}

.admonitionblock > table {
    border: 0;
    background: none;
    width: 100%;
}

.admonitionblock > table td.icon {
    text-align: center;
    width: 80px;
}

.admonitionblock > table td.icon img {
    max-width: none;
}

.admonitionblock > table td.icon .title {
    font-weight: bold;
    text-transform: uppercase;
}

.admonitionblock > table td.content {
    padding-left: 1.125em;
    padding-right: 1.25em;
    border-left: 1px solid #dddddd;
    color: #616366;
}

.admonitionblock > table td.content > :last-child > :last-child {
    margin-bottom: 0;
}

.exampleblock > .content {
    border-style: solid;
    border-width: 1px;
    border-color: #e6e6e6;
    margin-bottom: 1.25em;
    padding: 1.25em;
    background: white;
    -webkit-border-radius: 4px;
    border-radius: 4px;
}

.exampleblock > .content > :first-child {
    margin-top: 0;
}

.exampleblock > .content > :last-child {
    margin-bottom: 0;
}

.exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6, .exampleblock > .content p {
    color: #333333;
}

.exampleblock > .content h1, .exampleblock > .content h2, .exampleblock > .content h3, .exampleblock > .content #toctitle, .sidebarblock.exampleblock > .content > .title, .exampleblock > .content h4, .exampleblock > .content h5, .exampleblock > .content h6 {
    line-height: 1;
    margin-bottom: 0.625em;
}

.exampleblock > .content h1.subheader, .exampleblock > .content h2.subheader, .exampleblock > .content h3.subheader, .exampleblock > .content .subheader#toctitle, .sidebarblock.exampleblock > .content > .subheader.title, .exampleblock > .content h4.subheader, .exampleblock > .content h5.subheader, .exampleblock > .content h6.subheader {
    line-height: 1.4;
}

.exampleblock.result > .content {
    -webkit-box-shadow: 0 1px 8px #d9d9d9;
    box-shadow: 0 1px 8px #d9d9d9;
}

.sidebarblock {
    border-style: solid;
    border-width: 1px;
    border-color: #d9d9d9;
    margin-bottom: 1.25em;
    padding: 1.25em;
    background: #f2f2f2;
    -webkit-border-radius: 4px;
    border-radius: 4px;
}

.sidebarblock > :first-child {
    margin-top: 0;
}

.sidebarblock > :last-child {
    margin-bottom: 0;
}

.sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6, .sidebarblock p {
    color: #333333;
}

.sidebarblock h1, .sidebarblock h2, .sidebarblock h3, .sidebarblock #toctitle, .sidebarblock > .content > .title, .sidebarblock h4, .sidebarblock h5, .sidebarblock h6 {
    line-height: 1;
    margin-bottom: 0.625em;
}

.sidebarblock h1.subheader, .sidebarblock h2.subheader, .sidebarblock h3.subheader, .sidebarblock .subheader#toctitle, .sidebarblock > .content > .subheader.title, .sidebarblock h4.subheader, .sidebarblock h5.subheader, .sidebarblock h6.subheader {
    line-height: 1.4;
}

.sidebarblock > .content > .title {
    color: #3c3d3f;
    margin-top: 0;
    line-height: 1.5;
}

.exampleblock > .content > :last-child > :last-child, .exampleblock > .content .olist > ol > li:last-child > :last-child, .exampleblock > .content .ulist > ul > li:last-child > :last-child, .exampleblock > .content .qlist > ol > li:last-child > :last-child, .sidebarblock > .content > :last-child > :last-child, .sidebarblock > .content .olist > ol > li:last-child > :last-child, .sidebarblock > .content .ulist > ul > li:last-child > :last-child, .sidebarblock > .content .qlist > ol > li:last-child > :last-child {
    margin-bottom: 0;
}

.literalblock > .content pre, .listingblock > .content pre {
    background: url('./image/pre-bg.jpg');
    border-width: 0 0 1px 0;
    border-style: solid;
    border-color: #f0f0f0;
    -webkit-border-radius: 4px;
    border-radius: 4px;
    padding: 15px;
    word-wrap: break-word;
}

.literalblock > .content pre.nowrap, .listingblock > .content pre.nowrap {
    overflow-x: auto;
    white-space: pre;
    word-wrap: normal;
}

.literalblock > .content pre > code, .listingblock > .content pre > code {
    display: block;
}

@media only screen {
    .literalblock > .content pre, .listingblock > .content pre {
        font-size: 0.64em;
    }
}

@media only screen and (min-width: 768px) {
    .literalblock > .content pre, .listingblock > .content pre {
        font-size: 0.72em;
    }
}

@media only screen and (min-width: 1280px) {
    .literalblock > .content pre, .listingblock > .content pre {
        font-size: 0.8em;
    }
}

.listingblock > .content {
    position: relative;
}

.listingblock:hover code[class*=" language-"]:before {
    text-transform: uppercase;
    font-size: 0.9em;
    color: #999;
    position: absolute;
    top: 0.375em;
    right: 0.375em;
}

.listingblock:hover code.asciidoc:before {
    content: "asciidoc";
}

.listingblock:hover code.clojure:before {
    content: "clojure";
}

.listingblock:hover code.css:before {
    content: "css";
}

.listingblock:hover code.groovy:before {
    content: "groovy";
}

.listingblock:hover code.html:before {
    content: "html";
}

.listingblock:hover code.java:before {
    content: "java";
}

.listingblock:hover code.javascript:before {
    content: "javascript";
}

.listingblock:hover code.python:before {
    content: "python";
}

.listingblock:hover code.ruby:before {
    content: "ruby";
}

.listingblock:hover code.scss:before {
    content: "scss";
}

.listingblock:hover code.xml:before {
    content: "xml";
}

.listingblock:hover code.yaml:before {
    content: "yaml";
}

.listingblock.terminal pre .command:before {
    content: attr(data-prompt);
    padding-right: 0.5em;
    color: #999;
}

.listingblock.terminal pre .command:not([data-prompt]):before {
    content: '$';
}

table.pyhltable {
    border: 0;
    margin-bottom: 0;
}

table.pyhltable td {
    vertical-align: top;
    padding-top: 0;
    padding-bottom: 0;
}

table.pyhltable td.code {
    padding-left: .75em;
    padding-right: 0;
}

.highlight.pygments .lineno, table.pyhltable td:not(.code) {
    color: #999;
    padding-left: 0;
    padding-right: .5em;
    border-right: 1px solid #dddddd;
}

.highlight.pygments .lineno {
    display: inline-block;
    margin-right: .25em;
}

table.pyhltable .linenodiv {
    background-color: transparent !important;
    padding-right: 0 !important;
}

.quoteblock {
    margin: 0 0 0.9375em;
    padding: 0.5625em 1.25em 0 1.1875em;
    border-left: 1px solid #dddddd;
}

.quoteblock blockquote {
    margin: 0 0 0.9375em 0;
    padding: 0 0 0.5625em 0;
    border: 0;
}

.quoteblock blockquote > .paragraph:last-child p {
    margin-bottom: 0;
}

.quoteblock .attribution {
    margin-top: -.25em;
    padding-bottom: 0.5625em;
    font-size: inherit;
    color: #484a4c;
}

.quoteblock .attribution br {
    display: none;
}

.quoteblock .attribution cite {
    display: block;
    margin-bottom: 0.625em;
}

table thead th, table tfoot th {
    font-weight: bold;
}

table.tableblock.grid-all {
    border-collapse: separate;
    border-spacing: 1px;
    -webkit-border-radius: 4px;
    border-radius: 4px;
    border-top: 5px solid white;
    border-bottom: 5px solid white;
}

table.tableblock.frame-topbot, table.tableblock.frame-none {
    border-left: 0;
    border-right: 0;
}

table.tableblock.frame-sides, table.tableblock.frame-none {
    border-top: 0;
    border-bottom: 0;
}

table.tableblock td .paragraph:last-child p, table.tableblock td > p:last-child {
    margin-bottom: 0;
}

th.tableblock.halign-left, td.tableblock.halign-left {
    text-align: left;
}

th.tableblock.halign-right, td.tableblock.halign-right {
    text-align: right;
}

th.tableblock.halign-center, td.tableblock.halign-center {
    text-align: center;
}

th.tableblock.valign-top, td.tableblock.valign-top {
    vertical-align: top;
}

th.tableblock.valign-bottom, td.tableblock.valign-bottom {
    vertical-align: bottom;
}

th.tableblock.valign-middle, td.tableblock.valign-middle {
    vertical-align: middle;
}

p.tableblock.header {
    color: #222222;
    font-weight: bold;
}

td > div.verse {
    white-space: pre;
}

ol {
    margin-left: 1.75em;
}

ul li ol {
    margin-left: 1.5em;
}

dl dd {
    margin-left: 1.125em;
}

dl dd:last-child, dl dd:last-child > :last-child {
    margin-bottom: 0;
}

ol > li p, ul > li p, ul dd, ol dd, .olist .olist, .ulist .ulist, .ulist .olist, .olist .ulist {
    margin-bottom: 0.46875em;
}

ul.unstyled, ol.unnumbered, ul.checklist, ul.none {
    list-style-type: none;
}

ul.unstyled, ol.unnumbered, ul.checklist {
    margin-left: 0.625em;
}

ul.checklist li > p:first-child > i[class^="icon-check"]:first-child, ul.checklist li > p:first-child > input[type="checkbox"]:first-child {
    margin-right: 0.25em;
}

ul.checklist li > p:first-child > input[type="checkbox"]:first-child {
    position: relative;
    top: 1px;
}

ul.inline {
    margin: 0 auto 0.46875em auto;
    margin-left: -1.375em;
    margin-right: 0;
    padding: 0;
    list-style: none;
    overflow: hidden;
}

ul.inline > li {
    list-style: none;
    float: left;
    margin-left: 1.375em;
    display: block;
}

ul.inline > li > * {
    display: block;
}

.unstyled dl dt {
    font-weight: normal;
    font-style: normal;
}

ol.arabic {
    list-style-type: decimal;
}

ol.decimal {
    list-style-type: decimal-leading-zero;
}

ol.loweralpha {
    list-style-type: lower-alpha;
}

ol.upperalpha {
    list-style-type: upper-alpha;
}

ol.lowerroman {
    list-style-type: lower-roman;
}

ol.upperroman {
    list-style-type: upper-roman;
}

ol.lowergreek {
    list-style-type: lower-greek;
}

.hdlist > table, .colist > table {
    border: 0;
    background: none;
}

.hdlist > table > tbody > tr, .colist > table > tbody > tr {
    background: none;
}

td.hdlist1 {
    padding-right: .8em;
    font-weight: bold;
}

td.hdlist1, td.hdlist2 {
    vertical-align: top;
}

.literalblock + .colist, .listingblock + .colist {
    margin-top: -0.5em;
}

.colist > table tr > td:first-of-type {
    padding: 0 .8em;
    line-height: 1;
}

.colist > table tr > td:last-of-type {
    padding: 0.25em 0;
}

.qanda > ol > li > p > em:only-child {
    color: #247494;
}

.thumb, .th {
    line-height: 0;
    display: inline-block;
    border: solid 4px white;
    -webkit-box-shadow: 0 0 0 1px #dddddd;
    box-shadow: 0 0 0 1px #dddddd;
}

.imageblock.left, .imageblock[style*="float: left"] {
    margin: 0.25em 0.625em 1.25em 0;
}

.imageblock.right, .imageblock[style*="float: right"] {
    margin: 0.25em 0 1.25em 0.625em;
}

.imageblock > .title {
    margin-bottom: 0;
}

.imageblock.thumb, .imageblock.th {
    border-width: 6px;
}

.imageblock.thumb > .title, .imageblock.th > .title {
    padding: 0 0.125em;
}

.image.left, .image.right {
    margin-top: 0.25em;
    margin-bottom: 0.25em;
    display: inline-block;
    line-height: 0;
}

.image.left {
    margin-right: 0.625em;
}

.image.right {
    margin-left: 0.625em;
}

a.image {
    text-decoration: none;
}

span.footnote, span.footnoteref {
    vertical-align: super;
    font-size: 0.875em;
}

span.footnote a, span.footnoteref a {
    text-decoration: none;
}

#footnotes {
    padding-top: 0.75em;
    padding-bottom: 0.75em;
    margin-bottom: 0.625em;
}

#footnotes hr {
    width: 20%;
    min-width: 6.25em;
    margin: -.25em 0 .75em 0;
    border-width: 1px 0 0 0;
}

#footnotes .footnote {
    padding: 0 0.375em;
    line-height: 1.3;
    font-size: 0.875em;
    margin-left: 1.2em;
    text-indent: -1.2em;
    margin-bottom: .2em;
}

#footnotes .footnote a:first-of-type {
    font-weight: bold;
    text-decoration: none;
}

#footnotes .footnote:last-of-type {
    margin-bottom: 0;
}

#content #footnotes {
    margin-top: -0.625em;
    margin-bottom: 0;
    padding: 0.75em 0;
}

.gist .file-data > table {
    border: none;
    background: #fff;
    width: 100%;
    margin-bottom: 0;
}

.gist .file-data > table td.line-data {
    width: 99%;
}

div.unbreakable {
    page-break-inside: avoid;
}

.big {
    font-size: larger;
}

.small {
    font-size: smaller;
}

.underline {
    text-decoration: underline;
}

.overline {
    text-decoration: overline;
}

.line-through {
    text-decoration: line-through;
}

.aqua {
    color: #00bfbf;
}

.aqua-background {
    background-color: #00fafa;
}

.black {
    color: black;
}

.black-background {
    background-color: black;
}

.blue {
    color: #0000bf;
}

.blue-background {
    background-color: #0000fa;
}

.fuchsia {
    color: #bf00bf;
}

.fuchsia-background {
    background-color: #fa00fa;
}

.gray {
    color: #606060;
}

.gray-background {
    background-color: #7d7d7d;
}

.green {
    color: #006000;
}

.green-background {
    background-color: #007d00;
}

.lime {
    color: #00bf00;
}

.lime-background {
    background-color: #00fa00;
}

.maroon {
    color: #600000;
}

.maroon-background {
    background-color: #7d0000;
}

.navy {
    color: #000060;
}

.navy-background {
    background-color: #00007d;
}

.olive {
    color: #606000;
}

.olive-background {
    background-color: #7d7d00;
}

.purple {
    color: #600060;
}

.purple-background {
    background-color: #7d007d;
}

.red {
    color: #bf0000;
}

.red-background {
    background-color: #fa0000;
}

.silver {
    color: #909090;
}

.silver-background {
    background-color: #bcbcbc;
}

.teal {
    color: #006060;
}

.teal-background {
    background-color: #007d7d;
}

.white {
    color: #bfbfbf;
}

.white-background {
    background-color: #fafafa;
}

.yellow {
    color: #bfbf00;
}

.yellow-background {
    background-color: #fafa00;
}

span.icon > [class^="icon-"], span.icon > [class*=" icon-"] {
    cursor: default;
}

.admonitionblock td.icon [class^="icon-"]:before {
    font-size: 2.5em;
    text-shadow: 0 0 8px white, 1px 1px 2px rgba(0, 0, 0, 0.5);
    cursor: default;
}

.admonitionblock td.icon .icon-note:before {
    content: "\f05a";
    color: #2984a9;
    color: #1f637f;
}

.admonitionblock td.icon .icon-tip:before {
    content: "\f0eb";
    text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8);
    color: #111;
}

.admonitionblock td.icon .icon-warning:before {
    content: "\f071";
    color: #bf6900;
}

.admonitionblock td.icon .icon-caution:before {
    content: "\f06d";
    color: #bf3400;
}

.admonitionblock td.icon .icon-important:before {
    content: "\f06a";
    color: #bf0000;
}

.conum {
    display: inline-block;
    color: white !important;
    background-color: #515151;
    -webkit-border-radius: 100px;
    border-radius: 100px;
    text-align: center;
    width: 20px;
    height: 20px;
    font-size: 12px;
    font-weight: bold;
    line-height: 20px;
    font-family: Arial, sans-serif;
    font-style: normal;
    position: relative;
    top: -2px;
    letter-spacing: -1px;
}

.conum * {
    color: white !important;
}

.conum + b {
    display: none;
}

.conum:after {
    content: attr(data-value);
}

.conum:not([data-value]):empty {
    display: none;
}

body {
    background-image: url('./image/body-bg.jpg');
}

::selection {
    background-color: #fcc07f;
    color: #fff;
}

#header h1 {
    font-weight: bold;
}

.literalblock > .content > pre, .listingblock > .content > pre {
    -webkit-box-shadow: inset 0 1px 4px #aeb9b6;
    box-shadow: inset 0 1px 4px #aeb9b6;
    -webkit-border-radius: 5px;
    border-radius: 5px;
}

#content ul > li {
    list-style-type: square;
}

p > em {
    font-family: 'Noticia Text', serif;
    font-weight: 400;
    font-size: 95%;
}

.admonitionblock > table {
    width: 100%;
    background-image: url('./image/info-bg.jpg');
    border-collapse: separate;
    border-spacing: 0;
    -webkit-border-radius: 5px;
    border-radius: 5px;
    border: 1px solid #9EC6DF;
}

.admonitionblock > table td.icon {
    padding: 15px;
}

.admonitionblock > table td.icon .icon-tip:before {
    text-shadow: 0 0 20px white, 1px 1px 2px rgba(155, 155, 0, 0.8);
}

.admonitionblock > table td.content {
    font-family: 'Noticia Text', italic;
    font-size: 90%;
    font-style: italic;
    border: 0;
    padding: 15px;
}

.admonitionblock .literalblock > .content > pre, .admonitionblock .listingblock > .content > pre {
    background-image: url('./image/info-bg.jpg');
}

.exampleblock > .content {
    background-color: transparent;
    border-color: #c9c9c9;
}

.sidebarblock {
    background-image: url('../images/riak/sidebar-bg.jpg');
    -webkit-border-radius: 5px;
    border-radius: 5px;
}

.sidebarblock > .content > .title {
    color: #f1f1f1;
    text-shadow: 0px 2px 2px black;
    font-size: 1.25em;
    font-weight: bold;
}

.sidebarblock > .content ul, .sidebarblock > .content p {
    color: #f1f1f1;
    text-shadow: 0px 1px 1px black;
}

.sidebarblock > .content .title {
    color: #dfdfdf;
}

.sidebarblock > .content code {
    color: #333;
}

.sidebarblock > .content a {
    color: #60B5D8;
}

.sidebarblock > .content a:hover {
    color: #FAA94C;
}

.sidebarblock > .content .admonitionblock p, .sidebarblock > .content .admonitionblock ul {
    text-shadow: none;
    color: #616366;
}

table.tableblock.grid-all {
    -webkit-border-radius: 0;
    border-radius: 0;
    -webkit-box-shadow: 0 1px 3px #999999;
    box-shadow: 0 1px 3px #999999;
}

#footer {
    background-image: url('./image/footer-bg.jpg');
    padding: 25px 0;
}

#footer-text {
    color: #fff;
    text-shadow: 1px 1px 1px #333;
    font-size: 80%;
    text-align: center;
}

/*

Google Code style (c) Aahan Krish <geekpanth3r@gmail.com>

*/

/*pre code {
  display: block; padding: 0.5em;
  background: white; color: black;
}*/

pre .comment,
pre .template_comment,
pre .javadoc,
pre .comment * {
    color: #800;
}

pre .keyword,
pre .method,
pre .list .title,
pre .clojure .built_in,
pre .nginx .title,
pre .tag .title,
pre .setting .value,
pre .winutils,
pre .tex .command,
pre .http .title,
pre .request,
pre .status {
    color: #008;
}

pre .envvar,
pre .tex .special {
    color: #660;
}

pre .string,
pre .tag .value,
pre .cdata,
pre .filter .argument,
pre .attr_selector,
pre .apache .cbracket,
pre .date,
pre .regexp {
    color: #080;
}

pre .sub .identifier,
pre .pi,
pre .tag,
pre .tag .keyword,
pre .decorator,
pre .ini .title,
pre .shebang,
pre .prompt,
pre .hexcolor,
pre .rules .value,
pre .css .value .number,
pre .literal,
pre .symbol,
pre .ruby .symbol .string,
pre .number,
pre .css .function,
pre .clojure .attribute {
    color: #066;
}

pre .class .title,
pre .haskell .type,
pre .smalltalk .class,
pre .javadoctag,
pre .yardoctag,
pre .phpdoc,
pre .typename,
pre .tag .attribute,
pre .doctype,
pre .class .id,
pre .built_in,
pre .setting,
pre .params,
pre .variable,
pre .clojure .title {
    color: #606;
}

pre .css .tag,
pre .rules .property,
pre .pseudo,
pre .subst {
    color: #000;
}

pre .css .class, pre .css .id {
    color: #9B703F;
}

pre .value .important {
    color: #ff7700;
    font-weight: bold;
}

pre .rules .keyword {
    color: #C5AF75;
}

pre .annotation,
pre .apache .sqbracket,
pre .nginx .built_in {
    color: #9B859D;
}

pre .preprocessor,
pre .preprocessor * {
    color: #444;
}

pre .tex .formula {
    background-color: #EEE;
    font-style: italic;
}

pre .diff .header,
pre .chunk {
    color: #808080;
    font-weight: bold;
}

pre .diff .change {
    background-color: #BCCFF9;
}

pre .addition {
    background-color: #BAEEBA;
}

pre .deletion {
    background-color: #FFC8BD;
}

pre .comment .yardoctag {
    font-weight: bold;
}

.theme-controls {
    font-size: .8em;
    position: fixed;
    z-index: 1001;
    padding: 5px;
    background-color: white;
    box-shadow: 1px 1px 5px rgba(0, 0, 0, .35);
    text-align: right;

    /*top: 3px;*/
    bottom: 4px;
    right: 4px;
}

.theme-controls select {
    z-index: 1001;
}

.toc-left .theme-controls {
    /*right: 5px;*/
}

.toc-right .theme-controls {
    /*left: 5px;*/
}
</style>
<!-- Generate a nice TOC -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery.tocify/1.9.0/javascripts/jquery.tocify.min.js"></script>
<!-- We do not need the tocify CSS because the asciidoc CSS already provides most of what we neeed -->

<style>
    .tocify-header {
        font-style: italic;
    }

    .tocify-subheader {
        font-style: normal;
        font-size: 90%;
    }

    .tocify ul {
        margin: 0;
    }

    .tocify-focus {
        color: #faa94c;
    }

    .tocify-focus > a {
        color: #faa94c;
    }
</style>

<script type="text/javascript">
    $(function () {
        // Add a new container for the tocify toc into the existing toc so we can re-use its
        // styling
        $("#toc").append("<div id='generated-toc'></div>");
        $("#generated-toc").tocify({
            extendPage: true,
            context: "#content",
            highlightOnScroll: true,
            hideEffect: "slideUp",
            // Use the IDs that asciidoc already provides so that TOC links and intra-document
            // links are the same. Anything else might confuse users when they create bookmarks.
            hashGenerator: function(text, element) {
                return $(element).attr("id");
            },
            // Smooth scrolling doesn't work properly if we use the asciidoc IDs
            smoothScroll: false,
            // Set to 'none' to use the tocify classes
            theme: "none",
            // Handle book (may contain h1) and article (only h2 deeper)
            selectors: $( "#content" ).has( "h1" ).size() > 0 ? "h1,h2,h3,h4,h5" : "h2,h3,h4,h5",
            ignoreSelector: ".discrete"
        });

        // Switch between static asciidoc toc and dynamic tocify toc based on browser size
        // This is set to match the media selectors in the asciidoc CSS
        // Without this, we keep the dynamic toc even if it is moved from the side to preamble
        // position which will cause odd scrolling behavior
        var handleTocOnResize = function() {
            if ($(document).width() < 768) {
                $("#generated-toc").hide();
                $(".sectlevel0").show();
                $(".sectlevel1").show();
            }
            else {
                $("#generated-toc").show();
                $(".sectlevel0").hide();
                $(".sectlevel1").hide();
            }
        }

        $(window).resize(handleTocOnResize);
        handleTocOnResize();
    });
</script>
</head>
<body id="boot-features-security-oauth2" class="article toc2 toc-left">
<div id="header">
<h1>OAuth2 Boot</h1>
<div id="toc" class="toc2">
<div id="toctitle">Table of Contents</div>
<ul class="sectlevel1">
<li><a href="#boot-features-security-oauth2-authorization-server">1. 认证服务器（认证中心）</a>
<ul class="sectlevel2">
<li><a href="#_1_1_是否需要自己搭建一个认证服务器">1.1 是否需要自己搭建一个认证服务器？</a></li>
<li><a href="#_1_2_依赖项">1.2 依赖项</a></li>
<li><a href="#oauth2-boot-authorization-server-minimal">1.3 最小化配置</a>
<ul class="sectlevel3">
<li><a href="#_1_3_1_开启认证服务器">1.3.1 开启认证服务器</a></li>
<li><a href="#_1_3_2_指定_client_和_secret">1.3.2 指定 Client 和 Secret</a></li>
<li><a href="#_1_3_3_获取_token">1.3.3 获取 Token</a></li>
</ul>
</li>
<li><a href="#oauth2-boot-authorization-server-disable">1.4 如何关闭 Spring Boot Oauth2 的自动配置</a></li>
<li><a href="#oauth2-boot-authorization-server-authorization-code-grant">1.5 如何使授权码认证方式生效</a>
<ul class="sectlevel3">
<li><a href="#_1_5_1_添加用户">1.5.1 添加用户</a></li>
<li><a href="#_1_5_2_添加用户登录流">1.5.2 添加用户登录流</a></li>
<li><a href="#_1_5_3_客户端注册重定向uri">1.5.3 客户端注册重定向URI</a></li>
<li><a href="#oauth2-boot-testing-authorization-code-flow">1.5.4 测试授权码流程</a></li>
</ul>
</li>
<li><a href="#oauth2-boot-authorization-server-password-grant">1.6 如何使密码认证方式生效</a></li>
<li><a href="#oauth2-boot-authorization-server-authentication-manager">1.7 如何、何时提供 AuthenticationManager</a>
<ul class="sectlevel3">
<li><a href="#oauth2-boot-authorization-server-password-grant-user-details-service">1.7.1 Exposing a <code>UserDetailsService</code></a></li>
<li><a href="#oauth2-boot-authorization-server-password-grant-authentication-manager">1.7.2 Exposing an <code>AuthenticationManager</code></a></li>
<li><a href="#oauth2-boot-authorization-server-password-grant-authentication-configuration">1.7.3 Depending on <code>AuthenticationConfiguration</code></a></li>
<li><a href="#oauth2-boot-authorization-server-password-grant-autowired-authentication-manager">1.7.4 Manually Wiring An <code>AuthenticationManager</code></a></li>
</ul>
</li>
<li><a href="#oauth2-boot-authorization-server-spring-security-oauth2-resource-server">1.8 认证服务器是否与资源服务器或客户端互相兼容？</a>
<ul class="sectlevel3">
<li><a href="#oauth2-boot-authorization-server-spring-security-oauth2-resource-server-jwk">1.8.1 Configuring Authorization Server to Use JWKs</a></li>
<li><a href="#oauth2-boot-authorization-server-spring-security-oauth2-resource-server-jwk-set-uri">1.8.2 Add a JWK Set URI Endpoint</a></li>
<li><a href="#_1_8_3_testing_against_spring_security_5_1_resource_server">1.8.3 Testing Against Spring Security 5.1 Resource Server</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#boot-features-security-oauth2-resource-server">2. 资源服务器（应用服务器）</a>
<ul class="sectlevel2">
<li><a href="#_2_1_依赖项">2.1. 依赖项</a></li>
<li><a href="#oauth2-boot-resource-server-minimal">2.2. 最小化配置</a>
<ul class="sectlevel3">
<li><a href="#_2_2_1_开启资源服务器">2.2.1. 开启资源服务器</a></li>
<li><a href="#_2_2_2_指定token验证策略">2.2.2. 指定token验证策略</a>
<ul class="sectlevel4">
<li><a href="#_jwt">JWT</a></li>
<li><a href="#_opaque">Opaque</a></li>
</ul>
</li>
<li><a href="#_2_2_3_accessing_a_resource">2.2.3. Accessing a Resource</a></li>
</ul>
</li>
<li><a href="#oauth2-boot-resource-server-jwt-single-key">2.3. How to Use JWT with a Single Key</a></li>
<li><a href="#oauth2-boot-resource-server-token-info">2.4. 如何配置Token信息接口</a></li>
<li><a href="#oauth2-boot-resource-server-user-info">2.5. 如何配置用户信息接口</a>
<ul class="sectlevel3">
<li><a href="#oauth2-boot-resource-server-custom-user-info">2.5.1. 自定义用户信息请求</a></li>
</ul>
</li>
<li><a href="#oauth2-boot-resource-server-authorization">2.6. 自定义认证规则</a></li>
<li><a href="#oauth2-boot-resource-server-less-common">2.7. 不常用功能</a>
<ul class="sectlevel3">
<li><a href="#oauth2-boot-resource-server-token-type">2.7.1. Changing the Token Type</a></li>
<li><a href="#oauth2-boot-resource-server-filter-order">2.7.2. Changing the Filter Order</a></li>
<li><a href="#oauth2-boot-resource-server-permit-error">2.7.3. Permitting the /error Endpoint</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#boot-features-security-custom-user-info-client">3. 客户端</a></li>
<li><a href="#boot-features-security-oauth2-single-sign-on">4. 单点登录</a></li>
<li><a href="#common-application-properties">Appendix A: 通用配置文件</a></li>
</ul>
</div>
</div>
<div id="content">
<div id="preamble">
<div class="sectionbody">
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre>版本 2.3.0.RELEASE</pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>如果您已经在 classpath 中加入了 <code>spring-security-oauth2</code> ，就可以利用一些类库中自带的自动配置，很方便的配置认证和资源服务器。要想查看完整信息，请点击 <a href="https://projects.spring.io/spring-security-oauth/docs/oauth2.html">Spring Security OAuth 2 开发者指南</a></p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
<div class="paragraph">
<p><strong>下列项目仍处于维护阶段:</strong></p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>spring-security-oauth2</code></p>
</li>
<li>
<p><code>spring-security-oauth2-autoconfigure</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>当然，欢迎您使用它们，我们将为您提供帮助！</p>
</div>
<div class="paragraph">
<p><strong>但是，在选择 <code>spring-security-oauth2</code> 和 <code>spring-security-oauth2-autoconfigure</code> 之前，您应该查看 <a href="https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Features-Matrix">Spring Security的功能列表</a>，来看看是否能 <a href="https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2client">满足</a> 您的需求。</strong></p>
</div>
</td>
</tr>
</table>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
<div class="paragraph">
<p>该项目是 Spring Boot 1.x 中 Spring Security OAuth 的一部分。
<strong>在 Spring Boot 2.x 会被删除，以提供对 <a href="https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#oauth2client">Spring Security 5 OAuth 的支持。</a></strong></p>
</div>
<div class="paragraph">
<p>为了方便迁移，该项目作为旧版 Spring Security OAuth 与 Spring Boot 2.x 之间的桥梁而存在。</p>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="boot-features-security-oauth2-authorization-server">1. 认证服务器（认证中心）</h2>
<div class="sectionbody">
<div class="paragraph">
<p>使用 Spring Security OAuth2 Boot 可以快速搭建 OAuth 2.0 认证服务器。</p>
</div>
<div class="sect2">
<h3 id="_1_1_是否需要自己搭建一个认证服务器">1.1 是否需要自己搭建一个认证服务器？</h3>
<div class="paragraph">
<p>如果您想实现以下需求，可能就应该搭建自己的认证服务器了:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>您希望使用一个单独的服务(也叫 <em>统一身份</em> )来自行维护登录、注销以及找回密码等功能</p>
</li>
<li>
<p>您希望使用 OAuth 2.0 协议作为一个单独的服务来配合其它服务</p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="_1_2_依赖项">1.2 依赖项</h3>
<div class="paragraph">
<p>要想使用自动配置，需要依赖 <code>spring-security-oauth2</code> 和 <code>spring-security-oauth2-autoconfigure</code>。
注意您需要手动为 <code>spring-security-oauth2-autoconfigure</code> 指定版本，因为它在很久之前就已经不再由 Spring Boot 维护了，虽然它仍然和 Spring Boot 的版本相匹配。</p>
</div>
<div class="paragraph">
<p>如果您需要 JWT 支持，则需要依赖 <code>spring-security-jwt</code> 。</p>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-authorization-server-minimal">1.3 最小化配置</h3>
<div class="paragraph">
<p>通常来讲，创建一个最小化的 Spring Boot 认证服务器需要以下三个步骤:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>导入依赖项</p>
</li>
<li>
<p>使用 <code>@EnableAuthorizationServer</code> 注解</p>
</li>
<li>
<p>至少配置一对 client_id/client_secret</p>
</li>
</ol>
</div>
<div class="sect3">
<h4 id="_1_3_1_开启认证服务器">1.3.1 开启认证服务器</h4>
<div class="paragraph">
<p>与 Spring Boot 的 <code>@Enable</code> 注解类似，您需要在 包含 <code>main</code> 方法的类中添加 <code>@EnableAuthorizationServer</code> 注解，下面是一个例子:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableAuthorizationServer
@SpringBootApplication
public class SimpleAuthorizationServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SimpleAuthorizationServerApplication, args);
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>添加这个注解会引入其它的 Spring 配置文件，这些文件会添加一些默认的约定，例如应该如何对 token 进行签名、token的持续时间以及允许授权的范围。</p>
</div>
</div>
<div class="sect3">
<h4 id="_1_3_2_指定_client_和_secret">1.3.2 指定 Client 和 Secret</h4>
<div class="paragraph">
<p>根据 OAuth 2.0 规范，许多默认接口都需要 client 进行身份认证，因此您至少需要配置一个 client ，来让其他人能够和认证服务器进行交互。</p>
</div>
<div class="paragraph">
<p>下面这段代码向您展示了如何配置:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
    client:
      client-id: first-client
      client-secret: noonewilleverguess</code></pre>
</div>
</div>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
在生产环境中不能这样使用，您需要做更多的配置。
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>这样就完成啦！但是，我们如何使用它呢？请继续往下看。</p>
</div>
</div>
<div class="sect3">
<h4 id="_1_3_3_获取_token">1.3.3 获取 Token</h4>
<div class="paragraph">
<p>OAuth 2.0 本质上就是一个将长期生效的 token 交换为短期生效的 token 的框架。</p>
</div>
<div class="paragraph">
<p>默认情况下，只要添加了 <code>@EnableAuthorizationServer</code> 注解，就可以使用客户端授权 (client credentials) 策略了，这意味着您可以直接运行下面的命令来获取token:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=client_credentials -dscope=any</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>得到的响应大概长这样:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-json" data-lang="json">{
    "access_token" : "f05a1ea7-4c80-4583-a123-dc7a99415588",
    "token_type" : "bearer",
    "expires_in" : 43199,
    "scope" : "any"
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>任何支持 OAuth 2.0 标准协议的资源服务器，都可以使用上面的 token 并配置认证服务器。</p>
</div>
<div class="paragraph">
<p>接下来，你可以查看下列章节:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#oauth2-boot-authorization-server-disable">1.4 如何关闭 Spring Boot Oauth2 的自动配置</a></p>
</li>
<li>
<p><a href="#oauth2-boot-authorization-server-authorization-code-grant">1.5 如何使授权码认证方式生效</a></p>
</li>
<li>
<p><a href="#oauth2-boot-authorization-server-password-grant">1.6 如何使密码认证方式生效</a></p>
</li>
<li>
<p><a href="#oauth2-boot-authorization-server-authentication-manager">1.7 如何、何时提供 AuthenticationManager</a></p>
</li>
<li>
<p><a href="#oauth2-boot-authorization-server-spring-security-oauth2-resource-server">1.8 认证服务器是否与资源服务器或客户端互相兼容？</a></p>
</li>
<li>
<p><a href="https://projects.spring.io/spring-security-oauth/docs/oauth2.html#jwt-tokens">How to Configure for Jwt Tokens</a></p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-authorization-server-disable">1.4 如何关闭 Spring Boot Oauth2 的自动配置</h3>
<div class="paragraph">
<p>总的来说，OAuth2 Boot 会创建一个 <a href="https://projects.spring.io/spring-security-oauth/docs/oauth2.html#authorization-server-configuration"><code>AuthorizationServerConfigurer</code></a>  实例并完成一些默认操作：</p>
</div>
<div class="ulist">
<ul>
<li>
<p>注册一个 <code>NoOpPasswordEncoder</code> (覆盖 <a href="https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-password-encoding">Spring Security 默认的编码器</a>)</p>
</li>
<li>
<p>允许客户端使用任何一种服务器支持的授权方式: <code>authorization_code</code>, <code>password</code>, <code>client_credentials</code>, <code>implicit</code>, 或 <code>refresh_token</code></p>
</li>
</ul>
</div>
<div class="paragraph">
<p>Otherwise, it also tries to pick up a handful of beans, if they are defined&#8201;&#8212;&#8201;namely:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>AuthenticationManager</code>: For looking up end users (not clients)</p>
</li>
<li>
<p><code>TokenStore</code>: 用于生成和获取 tokens</p>
</li>
<li>
<p><code>AccessTokenConverter</code>: 将 token 转换成不同的格式，例如 JWT。</p>
</li>
</ul>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
虽然这篇文档已经涵盖了这些 bean 的功能，但还是更推荐阅读 <a href="https://projects.spring.io/spring-security-oauth/docs/oauth2.html">Spring Security OAuth</a> 来查看更详细的术语
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>If you expose a bean of type <code>AuthorizationServerConfigurer</code>, none of this is done automatically.</p>
</div>
<div class="paragraph">
<p>So, for example, if you need to configure more than one client, change their allowed grant types, or use something better than the no-op password encoder (highly recommended!), then you want to expose your own <code>AuthorizationServerConfigurer</code>, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired DataSource dataSource;

    protected void configure(ClientDetailsServiceConfigurer clients) {
        clients
            .jdbc(this.dataSource)
            .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>The preceding configuration causes OAuth2 Boot to no longer retrieve the client from environment properties and now falls back to the Spring Security password encoder default.</p>
</div>
<div class="paragraph">
<p>From here, you may want to learn more about:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#oauth2-boot-authorization-server-authorization-code-grant">1.5 如何使授权码认证方式生效</a></p>
</li>
<li>
<p><a href="#oauth2-boot-authorization-server-password-grant">1.6 如何使密码认证方式生效</a></p>
</li>
</ul>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-authorization-server-authorization-code-grant">1.5 如何使授权码认证方式生效</h3>
<div class="paragraph">
<p>默认配置情况下是支持授权码认证方式的，但缺没配置完全。</p>
</div>
<div class="paragraph">
<p>这是因为，除了与配置之外，授权码认证方式还需要:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>配置用户</p>
</li>
<li>
<p>用户登录流，以及</p>
</li>
<li>
<p>客户端注册重定向URI</p>
</li>
</ul>
</div>
<div class="sect3">
<h4 id="_1_5_1_添加用户">1.5.1 添加用户</h4>
<div class="paragraph">
<p>在由 Spring Security 保护的典型 Spring Boot 应用程序中， <a href="https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#tech-userdetailsservice">用户由 <code>UserDetailsService</code> 定义</a>。
在这方面，授权服务器没有什么不同，如下例所示：</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
            User.withDefaultPasswordEncoder()
                .username("enduser")
                .password("password")
                .roles("USER")
                .build());
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>注意，与典型的 Spring Security web 应用一样，用户在 <code>WebSecurityConfigurerAdapter</code> 实例中定义。</p>
</div>
</div>
<div class="sect3">
<h4 id="_1_5_2_添加用户登录流">1.5.2 添加用户登录流</h4>
<div class="paragraph">
<p>顺便说一下，添加 <code>WebSecurityConfigurerAdapter</code> 实例后，用户登录所需的全部准备就都完成了。
但请注意，这是 web 应用程序本身的配置，而不是 OAuth 2.0 API 的。</p>
</div>
<div class="paragraph">
<p>如果您想自定义登录页面、提供除了登录之外的其它功能，或是添加额外的支持，例如找回密码等， <code>WebSecurityConfigurerAdapter</code> 中可以配置。</p>
</div>
</div>
<div class="sect3">
<h4 id="_1_5_3_客户端注册重定向uri">1.5.3 客户端注册重定向URI</h4>
<div class="paragraph">
<p>OAuth2 Boot does not support configuring a redirect URI as a property&#8201;&#8212;&#8201;say, alongside <code>client-id</code> and <code>client-secret</code>.</p>
</div>
<div class="paragraph">
<p>To add a redirect URI, you need to specify the client by using either <code>InMemoryClientDetailsService</code> or <code>JdbcClientDetailsService</code>.</p>
</div>
<div class="paragraph">
<p>Doing either means <a href="#oauth2-boot-authorization-server-disable">replacing the OAuth2 Boot-provided <code>AuthorizationServerConfigurer</code></a> with your own, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    protected void configure(ClientDetailsServiceConfigurer clients) {
        clients
            .inMemory()
                .withClient("first-client")
                .secret(passwordEncoder().encode("noonewilleverguess"))
                .scopes("resource:read")
                .authorizedGrantTypes("authorization_code")
                .redirectUris("http://localhost:8081/oauth/login/client-app");
    }
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="oauth2-boot-testing-authorization-code-flow">1.5.4 测试授权码流程</h4>
<div class="paragraph">
<p>Testing OAuth can be tricky since it requires more than one server to see the full flow in action.
However, the first steps are straight-forward:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>Browse to <a href="http://localhost:8080/oauth/authorize?grant_type=authorization_code&amp;response_type=code&amp;client_id=first-client&amp;state=1234" class="bare">http://localhost:8080/oauth/authorize?grant_type=authorization_code&amp;response_type=code&amp;client_id=first-client&amp;state=1234</a></p>
</li>
<li>
<p>The application, if the user is not logged in, redirects to the login page, at <a href="http://localhost:8080/login" class="bare">http://localhost:8080/login</a></p>
</li>
<li>
<p>Once the user logs in, the application generates a code and redirects to the registered redirect URI&#8201;&#8212;&#8201;in this case, <a href="http://localhost:8081/oauth/login/client-app" class="bare">http://localhost:8081/oauth/login/client-app</a></p>
</li>
</ol>
</div>
<div class="paragraph">
<p>The flow could continue at this point by standing up any resource server that is configured for opaque tokens and is pointed at this authorization server instance.</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-authorization-server-password-grant">1.6 如何使密码认证方式生效</h3>
<div class="paragraph">
<p>With the default configuration, while the Password Flow is technically possible, it, like Authorization Code, is missing users.</p>
</div>
<div class="paragraph">
<p>That said, because the default configuration creates a user with a username of <code>user</code> and a randomly-generated password, you can hypothetically check the logs for the password and do the following:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=password -dscope=any -dusername=user -dpassword=the-password-from-the-logs</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>When you run that command, you should get a token back.</p>
</div>
<div class="paragraph">
<p>More likely, though, you want to specify a set of users.</p>
</div>
<div class="paragraph">
<p>As was stated in <a href="#oauth2-boot-authorization-server-authorization-code-grant">1.5 如何使授权码认证方式生效</a>, in Spring Security, users are typically specified in a <code>UserDetailsService</code> and this application is no different, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
            User.withDefaultPasswordEncoder()
                .username("enduser")
                .password("password")
                .roles("USER")
                .build());
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>This is all we need to do. We do not need to override <code>AuthorizationServerConfigurer</code>, because the client ID and secret are specified as environment properties.</p>
</div>
<div class="paragraph">
<p>So, the following should now work:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=password -dscope=any -dusername=enduser -dpassword=password</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-authorization-server-authentication-manager">1.7 如何、何时提供 AuthenticationManager</h3>
<div class="paragraph">
<p>This is a very common question and is not terribly intuitive when <code>AuthorizationServerEndpointsConfigurer</code> needs an <code>AuthenticationManager</code> instance to be specified.
简单一句话回答：仅当资源服务器使用 <a href="#_how_to_make_password_grant_flow_work">密码认证</a> 的方式时，才需要提供 <code>AuthenticationManager</code> 。</p>
</div>
<div class="paragraph">
<p>It helps to remember a few fundamentals:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>An <code>AuthenticationManager</code> is an abstraction for authenticating users. It typically needs some kind of <code>UserDetailsService</code> to be specified in order to be complete.</p>
</li>
<li>
<p>End users are specified in a <code>WebSecurityConfigurerAdapter</code>.</p>
</li>
<li>
<p>OAuth2 Boot, by default, automatically picks up any exposed <code>AuthenticationManager</code>.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>However, not all flows require an <code>AuthenticationManager</code> because not all flows have end users involved.
For example, the Client Credentials flow asks for a token based only on the client&#8217;s authority, not the end user&#8217;s.
And the Refresh Token flow asks for a token based only on the authority of a refresh token.</p>
</div>
<div class="paragraph">
<p>Also, not all flows specifically require the OAuth 2.0 API itself to have an <code>AuthenticationManager</code>, either.
For example, the Authorization Code and Implicit flows verify the user when they login (application flow), not when the token (OAuth 2.0 API) is requested.</p>
</div>
<div class="paragraph">
<p>Only the Resource Owner Password flow returns a code based off of the end user&#8217;s credentials.
This means that the Authorization Server only needs an <code>AuthenticationManager</code> when clients are using the Resource Owner Password flow.</p>
</div>
<div class="paragraph">
<p>The following example shows the Resource Owner Password flow:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">.authorizedGrantTypes("password", ...)</code></pre>
</div>
</div>
<div class="paragraph">
<p>In the preceding flow, your Authorization Server needs an instance of <code>AuthenticationManager</code>.</p>
</div>
<div class="paragraph">
<p>这里提供了几种方式来做到这一点 (<a href="#boot-features-security-oauth2-authorization-server">记住前面的原理</a>):</p>
</div>
<div class="ulist">
<ul>
<li>
<p>保持 OAuth2 Boot 处在默认状态 (无需覆盖 <code>AuthorizationServerConfigurer</code>) 之后 <a href="#oauth2-boot-authorization-server-password-grant-user-details-service">定义一个 <code>UserDetailsService</code> Bean</a>。</p>
</li>
<li>
<p>保持 OAuth2 Boot 处在默认状态，之后 <a href="#oauth2-boot-authorization-server-password-grant-authentication-manager">定义一个 <code>AuthenticationManager</code> Bean</a>。</p>
</li>
<li>
<p>覆盖 <code>AuthorizationServerConfigurerAdapter</code> (这会破坏 OAuth2 Boot 的默认状态)，之后 <a href="#oauth2-boot-authorization-server-password-grant-authentication-configuration">通过 <code>AuthenticationConfiguration</code></a> 获取 <code>AuthenticationManager</code>。</p>
</li>
<li>
<p>覆盖 <code>AuthorizationServerConfigurerAdapter</code> ，之后 <a href="#oauth2-boot-authorization-server-password-grant-autowired-authentication-manager">手动连接 <code>AuthenticationManager</code></a></p>
</li>
</ul>
</div>
<div class="sect3">
<h4 id="oauth2-boot-authorization-server-password-grant-user-details-service">1.7.1 Exposing a <code>UserDetailsService</code></h4>
<div class="paragraph">
<p>End users are specified in a <code>WebSecurityConfigurerAdapter</code> through a <code>UserDetailsService</code>.
So, if you use the OAuth2 Boot defaults (meaning you haven&#8217;t implemented a <code>AuthorizationServerConfigurer</code>), you can expose a <code>UserDetailsService</code> and be done, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired DataSource dataSource;

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new JdbcUserDetailsManager(this.dataSource);
    }
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="oauth2-boot-authorization-server-password-grant-authentication-manager">1.7.2 Exposing an <code>AuthenticationManager</code></h4>
<div class="paragraph">
<p>In case you need to do more specialized configuration of the <code>AuthenticationManager</code>, you can do so in the <code>WebSecurityConfigurerAdapter</code> and then expose it, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean(BeansId.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(customAuthenticationProvider());
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>If you use the OAuth2 Boot defaults, then it picks up the bean automatically.</p>
</div>
</div>
<div class="sect3">
<h4 id="oauth2-boot-authorization-server-password-grant-authentication-configuration">1.7.3 Depending on <code>AuthenticationConfiguration</code></h4>
<div class="paragraph">
<p>Any configured <code>AuthenticationManager</code> is available in <code>AuthenticationConfiguration</code>.
This means that, if you need to have an <code>AuthorizationServerConfigurer</code> (in which case you need to do your own autowiring), you can have it depend on <code>AuthenticationConfiguration</code> to get the <code>AuthenticationManager</code> bean, as the following class shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Component
public class CustomAuthorizationServerConfigurer extends
    AuthorizationServerConfigurerAdapter {

    AuthenticationManager authenticationManager;

    public CustomAuthorizationServerConfigurer(AuthenticationConfiguration authenticationConfiguration) {
        this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) {
        // .. your client configuration that allows the password grant
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager);
    }
}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new MyCustomUserDetailsService();
    }
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="oauth2-boot-authorization-server-password-grant-autowired-authentication-manager">1.7.4 Manually Wiring An <code>AuthenticationManager</code></h4>
<div class="paragraph">
<p>In the most sophisticated case, where the <code>AuthenticationManager</code> needs special configuration and you have your own <code>AuthenticationServerConfigurer</code>, then you need to both create your own <code>AuthorizationServerConfigurerAdapter</code> and your own <code>WebSecurityConfigurerAdapter</code>:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Component
public class CustomAuthorizationServerConfigurer extends
    AuthorizationServerConfigurerAdapter {

    AuthenticationManager authenticationManager;

    public CustomAuthorizationServerConfigurer(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) {
        // .. your client configuration that allows the password grant
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager);
    }
}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean(BeansId.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(customAuthenticationProvider());
    }
}</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-authorization-server-spring-security-oauth2-resource-server">1.8 认证服务器是否与资源服务器或客户端互相兼容？</h3>
<div class="paragraph">
<p>No, not out of the box.
Spring Security 5.1 supports only JWT-encoded JWK-signed authorization, and Authorization Server does not ship with a JWK Set URI.</p>
</div>
<div class="paragraph">
<p>Basic support is possible, though.</p>
</div>
<div class="paragraph">
<p>In order to configure Authorization Server to be compatible with Spring Security 5.1 Resource Server, for example, you need to do the following:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Configure it to use JWKs</p>
</li>
<li>
<p>Add a JWK Set URI endpoint</p>
</li>
</ul>
</div>
<div class="sect3">
<h4 id="oauth2-boot-authorization-server-spring-security-oauth2-resource-server-jwk">1.8.1 Configuring Authorization Server to Use JWKs</h4>
<div class="paragraph">
<p>To change the format used for access and refresh tokens, you can change out the <code>AccessTokenConverter</code> and the <code>TokenStore</code>, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableAuthorizationServer
@Configuration
public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {

	AuthenticationManager authenticationManager;
	KeyPair keyPair;

	public JwkSetConfiguration(AuthenticationConfiguration authenticationConfiguration,
			KeyPair keyPair) throws Exception {

		this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
		this.keyPair = keyPair;
	}

    // ... client configuration, etc.

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
		// @formatter:off
		endpoints
			.authenticationManager(this.authenticationManager)
			.accessTokenConverter(accessTokenConverter())
			.tokenStore(tokenStore());
		// @formatter:on
	}

	@Bean
	public TokenStore tokenStore() {
		return new JwtTokenStore(accessTokenConverter());
	}

	@Bean
	public JwtAccessTokenConverter accessTokenConverter() {
		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
		converter.setKeyPair(this.keyPair);
		return converter;
	}
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="oauth2-boot-authorization-server-spring-security-oauth2-resource-server-jwk-set-uri">1.8.2 Add a JWK Set URI Endpoint</h4>
<div class="paragraph">
<p>Spring Security OAuth does not support JWKs, nor does <code>@EnableAuthorizationServer</code> support adding more OAuth 2.0 API endpoints to its initial set.
However, we can add this with only a few lines.</p>
</div>
<div class="paragraph">
<p>First, you need to add another dependency: <code>com.nimbusds:nimbus-jose-jwt</code>. This gives you the appropriate JWK primitives.</p>
</div>
<div class="paragraph">
<p>Second, instead of using <code>@EnableAuthorizationServer</code>, you need to directly include its two <code>@Configuration</code> classes:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><code>AuthorizationServerEndpointsConfiguration</code>: The <code>@Configuration</code> class for configuring the OAuth 2.0 API endpoints, such as what format to use for the tokens.</p>
</li>
<li>
<p><code>AuthorizationServerSecurityConfiguration</code>: The <code>@Configuration</code> class for the access rules around those endpoints.
This is the one that you need to extend, as shown in the following example:</p>
</li>
</ul>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@FrameworkEndpoint
class JwkSetEndpoint {
	KeyPair keyPair;

	public JwkSetEndpoint(KeyPair keyPair) {
		this.keyPair = keyPair;
	}

	@GetMapping("/.well-known/jwks.json")
	@ResponseBody
	public Map&lt;String, Object&gt; getKey(Principal principal) {
		RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
		RSAKey key = new RSAKey.Builder(publicKey).build();
		return new JWKSet(key).toJSONObject();
	}
}</code></pre>
</div>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Configuration
class JwkSetEndpointConfiguration extends AuthorizationServerSecurityConfiguration {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		super.configure(http);
		http
			.requestMatchers()
				.mvcMatchers("/.well-known/jwks.json")
				.and()
			.authorizeRequests()
				.mvcMatchers("/.well-known/jwks.json").permitAll();
	}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>Then, since you do not need to change <code>AuthorizationServerEndpointsConfiguration</code>, you can <code>@Import</code> it instead of using <code>@EnableAuthorizationServer</code>, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Import(AuthorizationServerEndpointsConfiguration.class)
@Configuration
public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {

    // ... the rest of the configuration from the previous section
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_1_8_3_testing_against_spring_security_5_1_resource_server">1.8.3 Testing Against Spring Security 5.1 Resource Server</h4>
<div class="paragraph">
<p>Now you can POST to the <code>/oauth/token</code> endpoint (<a href="#oauth2-boot-testing-authorization-code-flow">as before</a>) to obtain a token and then present that to a <a href="https://github.com/spring-projects/spring-security/tree/master/samples/boot/oauth2resourceserver">Spring Security 5.1 Resource Server</a>.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="boot-features-security-oauth2-resource-server">2. 资源服务器（应用服务器）</h2>
<div class="sectionbody">
<div class="paragraph">
<p>Spring Security OAuth2 Boot 简化了对资源服务器（应用服务器）的配置，其中有两种不同的 Token 模式：JWT（Json Web Token）模式 和 暗箱(Opaque)模式。</p>
</div>
<div class="sect2">
<h3 id="_2_1_依赖项">2.1. 依赖项</h3>
<div class="paragraph">
<p>要想使用自动配置，需要依赖 <code>spring-security-oauth2</code> 和 <code>spring-security-oauth2-autoconfigure</code>。
注意您需要手动为 <code>spring-security-oauth2-autoconfigure</code> 指定版本，因为它在很久之前就已经不再由 Spring Boot 维护了，虽然它仍然和 Spring Boot 的版本相匹配。</p>
</div>
<div class="paragraph">
<p>如果您需要 JWT 支持，则需要依赖 <code>spring-security-jwt</code> 。</p>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-resource-server-minimal">2.2. 最小化配置</h3>
<div class="paragraph">
<p>创建一个最小化的 Spring Boot 资源服务器需要以下三个步骤:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>导入依赖项</p>
</li>
<li>
<p>使用 <code>@EnableResourceServer</code> 注解</p>
</li>
<li>
<p>Specifying a strategy for verifying the bearer token.</p>
</li>
</ol>
</div>
<div class="sect3">
<h4 id="_2_2_1_开启资源服务器">2.2.1. 开启资源服务器</h4>
<div class="paragraph">
<p>与 Spring Boot 的 <code>@Enable</code> 注解类似，您需要在 包含 <code>main</code> 方法的类中添加 <code>@EnableResourceServer</code> 注解，下面是一个例子:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableResourceServer
@SpringBootApplication
public class SimpleAuthorizationServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SimpleAuthorizationServerApplication, args);
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>添加这个注解会同时添加 <code>OAuth2AuthenticationProcessingFilter</code>，不过您还需要一些额外的配置来指定如何合适地审批校验 token。</p>
</div>
</div>
<div class="sect3">
<h4 id="_2_2_2_指定token验证策略">2.2.2. 指定token验证策略</h4>
<div class="paragraph">
<p>Bearer Tokens typically come in one of two forms: JWT-encoded or opaque. You will need to configure the resource server with one or the other strategy.</p>
</div>
<div class="sect4">
<h5 id="_jwt">JWT</h5>
<div class="paragraph">
<p>To indicate JWT, simply specify the JWK Set Uri hosted on your Authorization Server:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
    resource:
      jwk:
        key-set-uri: https://idp.example.com/.well-known/jwks.json</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>Instead of a JWK Set Uri, you can also specify a key.</p>
</div>
<div class="paragraph">
<p>Note that with this configuration, your authorization server needs to be up in order for Resource Server to start up.</p>
</div>
</div>
<div class="sect4">
<h5 id="_opaque">Opaque</h5>
<div class="paragraph">
<p>To indicate opaque, simply specify the Authorization Server endpoint that knows how to decode the token:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
    resource:
      token-info-uri: https://idp.example.com/oauth2/introspect</code></pre>
</div>
</div>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
It&#8217;s likely this endpoint requires some kind of authorization separate from the token itself, for example, client authentication.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>That&#8217;s it! But, what do you do with it? We cover that next.</p>
</div>
</div>
</div>
<div class="sect3">
<h4 id="_2_2_3_accessing_a_resource">2.2.3. Accessing a Resource</h4>
<div class="paragraph">
<p>To confirm that Resource Server is correctly processing tokens, you can add a simple controller endpoint like so:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@RestController
public class SimpleController {
	@GetMapping("/whoami")
	public String whoami(@AuthenticationPrincipal(expression="name") String name) {
		return name;
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>Then, obtain an active access token from your Authorization Server and present it to the Resource Server:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-bash" data-lang="bash">curl -H "Authorization: $TOKEN" http://localhost:8080/whoami</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>And you should see the value of the <code>user_name</code> attribute in the token.</p>
</div>
<div class="paragraph">
<p>From this point, you may want to learn more about three alternative ways to authenticate using bearer tokens:</p>
</div>
<div class="ulist">
<ul>
<li>
<p><a href="#oauth2-boot-resource-server-jwt-single-key">2.3. How to Use JWT with a Single Key</a></p>
</li>
<li>
<p><a href="#oauth2-boot-resource-server-token-info">2.4. 如何配置Token信息接口</a></p>
</li>
<li>
<p><a href="#oauth2-boot-resource-server-user-info">2.5. 如何配置用户信息接口</a></p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-resource-server-jwt-single-key">2.3. How to Use JWT with a Single Key</h3>
<div class="paragraph">
<p>Instead of a JWK Set endpoint, you may have a local key you want to configure for verification.
While this is weaker due to the key being static, it may be necessary in your situation.</p>
</div>
<div class="paragraph">
<p>Configuring the resource server with the appropriate symmetric key or PKCS#8 PEM-encoded public key is simple, as can be seen below:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
    resource:
      jwt:
        key-value: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC...
          -----END PUBLIC KEY-----</code></pre>
</div>
</div>
</div>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
The pipe in yaml indicates a multi-line property value.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>You can also instead supply a <code>key-store</code>, <code>key-store-password</code>, <code>key-alias</code>, and <code>key-password</code> properties.</p>
</div>
<div class="paragraph">
<p>Or you can use the <code>key-uri</code> endpoint to get the key remotely from your authorization server, which is something of a happy medium between static, local configuration and a JWK Set endpoint.</p>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-resource-server-token-info">2.4. 如何配置Token信息接口</h3>
<div class="paragraph">
<p>The token info endpoint, also sometimes called the introspection endpoint, likely requires some kind of client authentication, either Basic or Bearer.
Generally speaking, the bearer token in the <code>SecurityContext</code> won&#8217;t suffice since that is tied to the user.
Instead, you&#8217;ll need to specify credentials that represent this client, like so:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
    client:
      clientId: client-id
      clientSecret: client-secret
    resource:
      tokenInfoUri: https://idp.example.com/oauth2/check_token</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>By default, this will use Basic authentication, using the configured credentials, to authenticate against the token info endpoint.</p>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-resource-server-user-info">2.5. 如何配置用户信息接口</h3>
<div class="paragraph">
<p>It&#8217;s atypical for a resource server to need to call a user info endpoint.
This is because, fundamentally, a resource server is about authorizing a request, not authenticating it.
That said, it is at times necessary.</p>
</div>
<div class="paragraph">
<p>If you specify a user info endpoint like so:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
    resource:
      userInfoUri: https://idp.example.com/oauth2/userinfo</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>Then Resource Server will send it the bearer token that is part of the request and enhance the <code>Authentication</code> object with the result.</p>
</div>
<div class="sect3">
<h4 id="oauth2-boot-resource-server-custom-user-info">2.5.1. 自定义用户信息请求</h4>
<div class="paragraph">
<p>Internally, Resource Server uses an <code>OAuth2RestTemplate</code> to invoke the <code>/userinfo</code> endpoint.
At times, it may be necessary to add filters or perform other customization for this invocation.
To customize the creation of this bean, you can expose a <code>UserInfoRestTemplateCustomizer</code>, like so:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Bean
public UserInfoRestTemplateCustomizer customHeader() {
	return restTemplate -&gt;
			restTemplate.getInterceptors().add(new MyCustomInterceptor());
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>This bean will be handed to a <code>UserInfoTemplateFactory</code> which will add other configurations helpful to coordinating with the <code>/userinfo</code> endpoint.</p>
</div>
<div class="paragraph">
<p>And, of course, you can replace the <code>UserInfoTemplateFactory</code> completely, if you need complete control over `OAuth2RestTemplate&#8217;s configuration.</p>
</div>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-resource-server-authorization">2.6. 自定义认证规则</h3>
<div class="paragraph">
<p>和 Spring Security 的工作方式类似地，您可以通过 Spring Security OAuth 自带的接口自定义认证规则，例如:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">public class HasAuthorityConfig
		extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.authorizeRequests()
				.antMatchers("/flights/**").hasAuthority("#oauth2.hasScope('message:read')")
				.anyRequest().authenticated();
		// @formatter:on
	}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>不过这里需要注意的是，如果您的服务既是认证服务器，又是资源服务器，那么某些接口可能需要特殊处理。
为了避免覆盖默认接口(例如 <code>/token</code>)，最好将资源服务器的接口重构到不同的路径下，例如:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">public class ResourceServerEndpointConfig
		extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.antMatchers("/resourceA/**", "/resourceB/**")
			.authorizeRequests()
				.antMatchers("/resourceA/**").hasAuthority("#oauth2.hasScope('resourceA:read')")
				.antMatchers("/resourceB/**").hasAuthority("#oauth2.hasScope('resourceB:read')")
				.anyRequest().authenticated();
		// @formatter:on
	}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>上面的配置会作用到您的资源接口上，同时也不会影响到认证服务器默认的接口。</p>
</div>
</div>
<div class="sect2">
<h3 id="oauth2-boot-resource-server-less-common">2.7. 不常用功能</h3>
<div class="sect3">
<h4 id="oauth2-boot-resource-server-token-type">2.7.1. Changing the Token Type</h4>
<div class="paragraph">
<p>Google and certain other third-party identity providers are more strict about the token type name that is sent in the headers to the user info endpoint.
The default is <code>Bearer</code>, which suits most providers and matches the spec.
However, if you need to change it, you can set <code>security.oauth2.resource.token-type</code>.</p>
</div>
</div>
<div class="sect3">
<h4 id="oauth2-boot-resource-server-filter-order">2.7.2. Changing the Filter Order</h4>
<div class="paragraph">
<p>OAuth2 resources are protected by a filter chain with the order specified by <code>security.oauth2.resource.filter-order</code>.</p>
</div>
<div class="paragraph">
<p>By default the filters in <code>AuthorizationServerConfigurerAdapter</code> come first, followed by those in <code>ResourceServerConfigurerAdapter</code>, followed by those in <code>WebSecurityConfigurerAdapter</code>.</p>
</div>
<div class="paragraph">
<p>This means that <strong>all application endpoints will require bearer token authentication</strong> unless one of two things happens:</p>
</div>
<div class="olist arabic">
<ol class="arabic">
<li>
<p>The filter chain order is changed or</p>
</li>
<li>
<p>The <code>ResourceServerConfigurerAdapter</code> set of authorized requests is narrowed</p>
</li>
</ol>
</div>
<div class="paragraph">
<p>The first, changing the filter chain order, can be done by moving <code>WebSecurityConfigurerAdapter</code> in front of <code>ResourceServerConfigurerAdapter</code> like so:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Order(2)
@EnableWebSecurity
public WebSecurityConfig extends WebSecurityConfigurerAdapter {
	// ...
}</code></pre>
</div>
</div>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Resource Server&#8217;s default <code>@Order</code> value is 3 which is why the example sets Web&#8217;s <code>@Order</code> to 2, so that it&#8217;s evaluated earlier.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>While this may work, it&#8217;s a little odd since we may simply trade one problem:</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p><code>ResourceServerConfigurerAdapter</code> is handling requests it shouldn&#8217;t</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p>For another:</p>
</div>
<div class="quoteblock">
<blockquote>
<div class="paragraph">
<p><code>WebSecurityConfigurerAdapter</code> is handling requests it shouldn&#8217;t</p>
</div>
</blockquote>
</div>
<div class="paragraph">
<p>The more robust solution, then, is to indicate to <code>ResourceServerConfigurerAdapter</code> which endpoints should be secured by bearer token authentication.</p>
</div>
<div class="paragraph">
<p>For example, the following configures Resource Server to secure the web application endpoints that begin with <code>/rest</code>:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@EnableResourceServer
public ResourceServerConfig extends ResourceServerConfigurerAdapter {
	@Override
    protected void configure(HttpSecurity http) {
        http
            .requestMatchers()
                .antMatchers("/rest/**")
            .authorizeRequests()
                .anyRequest().authenticated();
    }
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="sect3">
<h4 id="oauth2-boot-resource-server-permit-error">2.7.3. Permitting the /error Endpoint</h4>
<div class="paragraph">
<p>Resource Server, when also configured as a client, may rely on a request-scoped <code>OAuth2ClientContext</code> bean during the authentication process.
And, in some error situations, Resource Server forwards to the ERROR servlet dispatcher.</p>
</div>
<div class="paragraph">
<p>By default, request-scoped beans aren&#8217;t available in the ERROR dispatch.
And, because of this, you may see a complaint about the <code>OAuth2ClientContext</code> bean not being available.</p>
</div>
<div class="paragraph">
<p>The simplest approach may be to permit the <code>/error</code> endpoint, so that Resource Server doesn&#8217;t try and authenticate the request:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">public class PermitErrorConfig extends ResourceServerConfigurerAdapter {
    @Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.authorizeRequests()
				.antMatchers("/error").permitAll()
				.anyRequest().authenticated();
		// @formatter:on
	}
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>Other solutions are to configure Spring so that the <code>RequestContextFilter</code> is registered with the error dispatch or to register a <code>RequestContextListener</code> bean.</p>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="boot-features-security-custom-user-info-client">3. 客户端</h2>
<div class="sectionbody">
<div class="paragraph">
<p>To make your web application into an OAuth2 client, you can add <code>@EnableOAuth2Client</code> and
Spring Boot creates an <code>OAuth2ClientContext</code> and <code>OAuth2ProtectedResourceDetails</code> that
are necessary to create an <code>OAuth2RestOperations</code>. Spring Boot does not automatically
create such a bean, but you can easily create your own, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
        OAuth2ProtectedResourceDetails details) {
    return new OAuth2RestTemplate(details, oauth2ClientContext);
}</code></pre>
</div>
</div>
</div>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
You may want to add a qualifier and review your configuration, as more than one
<code>RestTemplate</code> may be defined in your application.
</td>
</tr>
</table>
</div>
<div class="paragraph">
<p>This configuration uses <code>security.oauth2.client.*</code> as credentials (the same as you might
be using in the Authorization Server). However, in addition, it needs to know the
authorization and token URIs in the Authorization Server, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="title">application.yml</div>
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
    client:
      clientId: bd1c0a783ccdd1c9b9e4
      clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
      accessTokenUri: https://github.com/login/oauth/access_token
      userAuthorizationUri: https://github.com/login/oauth/authorize
      clientAuthenticationScheme: form</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>An application with this configuration redirects to Github for authorization when you
attempt to use the <code>OAuth2RestTemplate</code>. If you are already signed into Github. you should not
even notice that it has authenticated.  These specific credentials work only if your
application is running on port 8080 (you can register your own client application in Github or other
provider for more flexibility).</p>
</div>
<div class="paragraph">
<p>To limit the scope that the client asks for when it obtains an access token, you can set
<code>security.oauth2.client.scope</code> (comma separated or an array in YAML). By default, the scope
is empty, and it is up to Authorization Server to decide what the defaults should be
(usually depending on the settings in the client registration that it holds).</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
There is also a setting for <code>security.oauth2.client.client-authentication-scheme</code>,
which defaults to <code>header</code> (but you might need to set it to <code>form</code> if, like Github for
instance, your OAuth2 provider does not like header authentication). In fact, the
<code>security.oauth2.client.*</code> properties are bound to an instance of
<code>AuthorizationCodeResourceDetails</code>, so all of its properties can be specified.
</td>
</tr>
</table>
</div>
<div class="admonitionblock tip">
<table>
<tr>
<td class="icon">
<div class="title">Tip</div>
</td>
<td class="content">
In a non-web application, you can still create an <code>OAuth2RestOperations</code>, and it
is still wired into the <code>security.oauth2.client.*</code> configuration. In this case, you are asking for is a
&#8220;client credentials token grant&#8221; if you use it (and there is no
need to use <code>@EnableOAuth2Client</code> or <code>@EnableOAuth2Sso</code>). To prevent that infrastructure
being defined, remove the <code>security.oauth2.client.client-id</code> from your configuration
(or make it be an empty string).
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="boot-features-security-oauth2-single-sign-on">4. 单点登录</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You can use an OAuth2 Client to fetch user details from the provider (if such features are
available) and then convert them into an <code>Authentication</code> token for Spring Security.
The Resource Server (<a href="#boot-features-security-oauth2-resource-server">described earlier</a>) supports this through the <code>user-info-uri</code> property. This is the basis
for a Single Sign On (SSO) protocol based on OAuth2, and Spring Boot makes it easy to
participate by providing an annotation <code>@EnableOAuth2Sso</code>. The Github client shown in the preceding section can
protect all its resources and authenticate by using the Github <code>/user/</code> endpoint, by adding
that annotation and declaring where to find the endpoint (in addition to the
<code>security.oauth2.client.*</code> configuration already listed earlier):</p>
</div>
<div class="exampleblock">
<div class="title">Example 1. application.yml</div>
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-yaml" data-lang="yaml">security:
  oauth2:
# ...
  resource:
    userInfoUri: https://api.github.com/user
    preferTokenInfo: false</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>Since all paths are secure by default, there is no &#8220;home&#8221; page that you can show to
unauthenticated users and invite them to login (by visiting the <code>/login</code> path, or the
path specified by <code>security.oauth2.sso.login-path</code>).</p>
</div>
<div class="paragraph">
<p>To customize the access rules or paths to protect s(o you can add a &#8220;home&#8221; page for
instance,) you can add <code>@EnableOAuth2Sso</code> to a <code>WebSecurityConfigurerAdapter</code>. The
annotation causes it to be decorated and enhanced with the necessary pieces to get
the <code>/login</code> path working. In the following example, we simply allow unauthenticated access
to the home page at <code>/</code> and keep the default for everything else:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .mvcMatchers("/").permitAll()
                .anyRequest().authenticated();
    }
}</code></pre>
</div>
</div>
</div>
</div>
<div class="paragraph">
<p>Also, note that, since all endpoints are secure by default, this includes any default
error handling endpoints&#8201;&#8212;&#8201;for example, the <code>/error</code> endpoint. This means that, if
there is some problem during Single Sign On that requires the application to redirect
to the <code>/error</code> page, this can cause an infinite redirect between the identity
provider and the receiving application.</p>
</div>
<div class="paragraph">
<p>First, think carefully about making an endpoint insecure, as you may find that the
behavior is simply evidence of a different problem. However, this behavior can be
addressed by configuring the application to permit <code>/error</code>, as the following example shows:</p>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-java" data-lang="java">@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/error").permitAll()
                .anyRequest().authenticated();
    }
}</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="sect1">
<h2 id="common-application-properties">Appendix A: 通用配置文件</h2>
<div class="sectionbody">
<div class="paragraph">
<p>You can specify various properties inside your <code>application.properties</code> or <code>application.yml</code>
files or as command line switches. This section provides a list of common Spring Boot
properties and references to the underlying classes that consume them.</p>
</div>
<div class="admonitionblock note">
<table>
<tr>
<td class="icon">
<div class="title">Note</div>
</td>
<td class="content">
Property contributions can come from additional jar files on your classpath, so
you should not consider this an exhaustive list. It is also perfectly legitimate to define
your own properties.
</td>
</tr>
</table>
</div>
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<div class="title">Warning</div>
</td>
<td class="content">
This sample file is meant as a guide only. Do <strong>not</strong> copy and paste the entire
content into your application. Rather, pick only the properties that you need.
</td>
</tr>
</table>
</div>
<div class="exampleblock">
<div class="content">
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-properties" data-lang="properties"># SECURITY OAUTH2 CLIENT (<a href="../../api/org/springframework/boot/autoconfigure/security/oauth2/OAuth2ClientProperties.html">OAuth2ClientProperties</a>)
security.oauth2.client.client-id= # OAuth2 client id.
security.oauth2.client.client-secret= # OAuth2 client secret. A random secret is generated by default

# SECURITY OAUTH2 RESOURCES (<a href="../../api/org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerProperties.html">ResourceServerProperties</a>)
security.oauth2.resource.id= # Identifier of the resource.
security.oauth2.resource.jwt.key-uri= # The URI of the JWT token. Can be set if the value is not available and the key is public.
security.oauth2.resource.jwt.key-value= # The verification key of the JWT token. Can either be a symmetric secret or PEM-encoded RSA public key.
security.oauth2.resource.jwk.key-set-uri= # The URI for getting the set of keys that can be used to validate the token.
security.oauth2.resource.prefer-token-info=true # Use the token info, can be set to false to use the user info.
security.oauth2.resource.service-id=resource #
security.oauth2.resource.token-info-uri= # URI of the token decoding endpoint.
security.oauth2.resource.token-type= # The token type to send when using the userInfoUri.
security.oauth2.resource.user-info-uri= # URI of the user endpoint.

# SECURITY OAUTH2 SSO (<a href="../../api/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2SsoProperties.html">OAuth2SsoProperties</a>)
security.oauth2.sso.login-path=/login # Path to the login page, i.e. the one that triggers the redirect to the OAuth2 Authorization Server</code></pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>